/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tdom-internal.h"
#include "tdom.h"
#include "tdom_writer.h"

#define SUM(var, callable) do { \
    int r = (callable); \
    if (r < 0) \
        return r; \
    var += r; \
} while (0);

static
int tdom_write_indent(struct evbuffer *buffer, int indent, int indent_size)
{
    int ret = 0;
    int i;

    SUM(ret, evbuffer_add_printf(buffer, "\n"));
    for (i = 0; i < (indent * indent_size); i++) {
        SUM(ret, evbuffer_add_printf(buffer, " "));
    }

    return ret;
}

static
int tdom_write_ns(struct evbuffer *buffer, struct tdom_ns *ns)
{
    if (*ns->prefix == '\0') {
        return evbuffer_add_printf(buffer, " xmlns=\"%s\"", ns->uri);
    } else {
        return evbuffer_add_printf(buffer, " xmlns:%s=\"%s\"", ns->prefix, ns->uri);
    }
}

static
int tdom_write_attr(struct evbuffer *buffer, struct tdom_attr *attr)
{
    int ret = 0;
    int need;
    int len;
    int readsize;
    int n;
#ifndef USE_LIBEVENT_1
    char *tmp;
#endif /* USE_LIBEVENT_1 */

    SUM(ret, evbuffer_add_printf(buffer, " %s=\"", attr->node.name));
    need = tdom_count_escaped_len(attr->value, strlen(attr->value));
#ifdef USE_LIBEVENT_1
    evbuffer_expand(buffer, need);
    len = strlen(attr->value);
    n = need;
    readsize = tdom_escape(attr->value, len, (char *)&buffer->buffer[buffer->off], &need);
    if (readsize != len) {
        return -1;
    }
    SUM(ret, n);
    buffer->off += n;
#else /* USE_LIBEVENT_1 */
    tmp = (char*)malloc(need);
    if (tmp == NULL) {
        return -1;
    }
    len = strlen(attr->value);
    n = need;
    readsize = tdom_escape(attr->value, len, tmp, &need);
    if (readsize != len) {
        return -1;
    }
    if (evbuffer_add(buffer, tmp, n) != 0) {
        free(tmp);
        return -1;
    }
    free(tmp);
    SUM(ret, n);
#endif /* USE_LIBEVENT_1 */
    SUM(ret, evbuffer_add_printf(buffer, "\""));

    return ret;
}

static
int tdom_write_text(struct evbuffer *buffer, struct tdom_text *text)
{
    int ret = 0;
    int need;
    int len;
    int readsize;
    int n;
#ifndef USE_LIBEVENT_1
    char *tmp;
#endif /* USE_LIBEVENT_1 */

    need = tdom_count_escaped_len(text->value, strlen(text->value));
#ifdef USE_LIBEVENT_1
    evbuffer_expand(buffer, need);
    len = strlen(text->value);
    n = need;
    readsize = tdom_escape(text->value, len, (char *)&buffer->buffer[buffer->off], &need);
    if (readsize != len) {
        return -1;
    }
    SUM(ret, n);
    buffer->off += n;
#else /* USE_LIBEVENT_1 */
    tmp = (char*)malloc(need);
    if (tmp == NULL) {
        return -1;
    }
    len = strlen(text->value);
    n = need;
    readsize = tdom_escape(text->value, len, tmp, &need);
    if (readsize != len) {
        return -1;
    }
    if (evbuffer_add(buffer, tmp, n) != 0) {
        free(tmp);
        return -1;
    }
    free(tmp);
    SUM(ret, n);
#endif /* USE_LIBEVENT_1 */

    return ret;
}

static
int tdom_write_elem(struct evbuffer *buffer,
                    struct tdom_elem *elem,
                    int indent,
                    int indent_size)
{
    int ret = 0;
    int children = 0;
    int ntext = 0;
    int nelem = 0;
    struct tdom_node *node;
    struct tdom_ns *ns;

    SUM(ret, tdom_write_indent(buffer, indent, indent_size));
    SUM(ret, evbuffer_add_printf(buffer, "<%s", elem->node.name));

    TAILQ_FOREACH(ns, &elem->nss, sibling) {
        SUM(ret, tdom_write_ns(buffer, ns));
    }

    TAILQ_FOREACH(node, &elem->attrs, sibling) {
        SUM(ret, tdom_write_attr(buffer, (struct tdom_attr *)node));
    }

    TAILQ_FOREACH(node, &elem->children, sibling) {
        if (node->type == TDOM_TEXT) {
            ntext++;
        }
        children++;
    }

    if (children == 0) {
        SUM(ret, evbuffer_add_printf(buffer, "/>"));
        return ret;
    }

    SUM(ret, evbuffer_add_printf(buffer, ">"));

    indent++;
    TAILQ_FOREACH(node, &elem->children, sibling) {
        if (node->type == TDOM_ELEM) {
            nelem++;
            SUM(ret, tdom_write_elem(buffer, (struct tdom_elem *)node, indent, indent_size));
        } else if (node->type == TDOM_TEXT) {
            SUM(ret, tdom_write_text(buffer, (struct tdom_text *)node));
        }
    }
    indent--;

    if (ntext == 0 || nelem != 0) {
        SUM(ret, tdom_write_indent(buffer, indent, indent_size));
    }
    SUM(ret, evbuffer_add_printf(buffer, "</%s>", elem->node.name));

    return ret;
}

static
int tdom_write_xmldecl(struct evbuffer *buffer, struct tdom_doc *doc)
{
    const char *ver = doc->version;
    const char *enc = doc->encoding;

    if (ver == NULL) {
        ver = "1.0";
    }
    if (enc == NULL) {
        return evbuffer_add_printf(buffer, "<?xml version=\"%s\"?>", ver);
    }

    return evbuffer_add_printf(buffer, "<?xml version=\"%s\" encoding=\"%s\"?>", ver, enc);
}

/* public functions */

struct tdom_writer *tdom_writer_new()
{
    struct tdom_writer *writer;

    if ((writer = malloc(sizeof(struct tdom_writer)))) {
        writer->doc = NULL;
        writer->indent_size = 2;
    }

    return writer;
}

void tdom_writer_delete(struct tdom_writer *writer)
{
    free(writer);
}

void tdom_writer_set(struct tdom_writer *writer, struct tdom_doc *doc)
{
    if (writer) {
        writer->doc = doc;
    }
}

int tdom_writer_write_all(struct evbuffer *buffer, struct tdom_doc *doc)
{
    int ret;
    int total = 0;
    struct tdom_writer *writer;

    if (buffer == NULL || doc == NULL) {
        return -1;
    }

    if ((writer = tdom_writer_new()) == NULL) {
        return -2;
    }
    tdom_writer_set(writer, doc);

    if ((ret = tdom_write_xmldecl(buffer, writer->doc)) < 0) {
        goto out;
    }
    total += ret;

    if ((ret = tdom_write_elem(buffer, doc->root, 0, writer->indent_size)) < 0) {
        goto out;
    }
    total += ret;

    if ((ret = evbuffer_add_printf(buffer, "\n")) < 0) {
        goto out;
    }
    ret = total + ret;

out:
    tdom_writer_delete(writer);
    return ret;
}

#if 0
int tdom_writer_write_all(struct tdom_writer *writer, struct evbuffer *buffer)
{
    int ret = 0;

    tdom_assert(writer);
    tdom_assert(buffer);

    SUM(ret, tdom_write_xmldecl(buffer, writer->doc));
    SUM(ret, tdom_write_elem(buffer, writer->doc->root, 0, writer->indent_size));
    SUM(ret, evbuffer_add_printf(buffer, "\n"));

    return ret;
}
#endif

